/*
 * Copyright (C) 2019 ByteDance Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bytedance.scene.group;

import com.bytedance.scene.*;
import com.bytedance.scene.animation.AnimationOrAnimator;
import com.bytedance.scene.animation.AnimationOrAnimatorFactory;
import com.bytedance.scene.interfaces.ChildSceneLifecycleCallbacks;
import com.bytedance.scene.utlity.Experimental;
import com.bytedance.scene.utlity.NonNullPair;
import com.bytedance.scene.utlity.SceneInstanceUtility;
import com.bytedance.scene.utlity.ThreadUtility;
import com.bytedance.scene.utlity.*;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.IntentParams;
import ohos.agp.animation.Animator;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.LayoutScatter;
import ohos.agp.utils.TextTool;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


/**
 * Created by JiangQi on 8/1/18.
 * <p>
 * The entire process of the synchronize child Scene:
 * <p>
 * When entering:
 * <p>
 * +----------------------+     +--------------------+     +--------------------------+     +-------------------------+     +----------------+     +---------------+     +-----------------+     +----------------+
 * | Parent onViewCreated | --> |       sync_1       | --> | Parent onActivityCreated | --> |         sync_2          | --> | Parent onStart | --> |    sync_3     | --> | Parent onResume | --> |     sync_4     | ---
 * +----------------------+     +--------------------+     +--------------------------+     +-------------------------+     +----------------+     +---------------+     +-----------------+     +----------------+
 * |                                                           |                                                      |                                             |
 * | sync                                                      | sync                                                 | sync                                        | sync
 * v                                                           v                                                      v                                             v
 * +--------------------+     +--------------------------+     +-------------------------+                            +---------------+                             +----------------+
 * | Child onCreateView | --> |   Child onViewCreated    |     | Child onActivityCreated |                            | Child onStart |                             | Child onResume |
 * +--------------------+     +--------------------------+     +-------------------------+                            +---------------+                             +----------------+
 * <p>
 * <p>
 * <p>
 * When exiting:
 * <p>
 * +---------------+     +----------------+     +--------------+     +---------------+     +---------------------+     +----------------------+
 * | Child onPause | --> |     sync_1     | --> | Child onStop | --> |    sync_2     | --> | Child onDestroyView | --> |        sync_3        | ---
 * +---------------+     +----------------+     +--------------+     +---------------+     +---------------------+     +----------------------+
 * |                                           |                                                 |
 * | sync                                      | sync                                            | sync
 * v                                           v                                                 v
 * +----------------+                          +---------------+                                 +----------------------+
 * | Parent onPause |                          | Parent onStop |                                 | Parent onDestroyView |
 * +----------------+                          +---------------+                                 +----------------------+
 * <p>
 * <p>
 * TODO: Must support transaction, so we can add and hide without trigger onResume().
 * Otherwise, ViewPager will be difficult to handle.
 * <p>
 * <p>
 * common usage
 * ```
 * class TestChildScene : Scene() {
 * override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View {
 * return View(requireSceneContext())
 * }
 * }
 * <p>
 * class TestGroupScene : GroupScene() {
 * override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): ViewGroup {
 * val layout = FrameLayout(requireSceneContext())
 * layout.id = View.generateViewId()
 * return layout
 * }
 * <p>
 * override fun onActivityCreated(savedInstanceState: Bundle?) {
 * super.onActivityCreated(savedInstanceState)
 * add(view.id, TestChildScene(), "Child_TAG")
 * }
 * }
 * ```
 */
public abstract class GroupScene extends Scene implements SceneParent {
    private static final String KEY_GROUP_SCENE_SUPPORT_RESTORE_ARGUMENT = "bd-scene-group:support_restore";
    private final GroupSceneManager mGroupSceneManager;

    private final List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> mLifecycleCallbacks = new ArrayList<>();
    private boolean mSupportRestore = true;

    public GroupScene() {
        this.mGroupSceneManager = new GroupSceneManager(this);
    }


    GroupSceneManager getGroupSceneManager() {
        return mGroupSceneManager;
    }


    @Override
    public final List<Scene> getSceneList() {
        return this.mGroupSceneManager.getChildSceneList();
    }

    public final void beginTransaction() {
        this.mGroupSceneManager.beginTransaction();
    }

    public final void commitTransaction() {
        this.mGroupSceneManager.commitTransaction();
    }

    private static final AnimationOrAnimatorFactory EMPTY_ANIMATION_FACTORY = new AnimationOrAnimatorFactory() {
        @Override
        public AnimationOrAnimator getAnimationOrAnimator() {
            return null;
        }
    };

    public final void add(final int viewId, final Scene scene, final String tag) {
        add(viewId, scene, tag, EMPTY_ANIMATION_FACTORY);
    }

    public final void add(final int viewId, final Scene scene, final String tag, Animator nextAnim) {
        add(viewId, scene, tag, buildAnimatorFactory(scene, nextAnim));
    }

    private void add(final int viewId, final Scene scene, final String tag, AnimationOrAnimatorFactory animationOrAnimatorFactory) {
        ThreadUtility.checkUIThread();

        if (TextTool.isNullOrEmpty(tag)) {
            throw new IllegalArgumentException("tag can't be empty");
        }

        if (scene == this) {
            throw new IllegalArgumentException("GroupScene can't be added to itself");
        }

        /*
         * TODO: It is possible for GroupScene to perform find + add 2 times,
         *       as the operations are all cached. (Fragment has this problem too)
         */

        if (isAdded(scene)) {
            int currentSceneContainerViewId = mGroupSceneManager.findSceneViewId(scene);
            if (currentSceneContainerViewId != viewId) {
                String currentViewIdName = null;
                try {
                    currentViewIdName = getResources().getElement(currentSceneContainerViewId).getString();
                } catch (NotExistException | WrongTypeException | IOException e) {
                    currentViewIdName = String.valueOf(currentSceneContainerViewId);
                }
                throw new IllegalArgumentException("Scene is already added to another container, viewId " + currentViewIdName);
            }

            String currentSceneTag = mGroupSceneManager.findSceneTag(scene);
            if (!currentSceneTag.equals(tag)) {
                throw new IllegalArgumentException("Scene is already added, tag " + currentSceneTag);
            }
        } else {
            Scene previousScene = findSceneByTag(tag);
            if (previousScene != null) {
                throw new IllegalArgumentException("already have a Scene " + previousScene.toString() + " with tag " + tag);
            }
        }

        if (scene.getParentScene() != null && scene.getParentScene() != this) {
            throw new IllegalArgumentException("Scene already has a parent, parent " + scene.getParentScene());
        }

        if (isSupportRestore() && !SceneInstanceUtility.isSupportRestore(scene)) {
            throw new IllegalArgumentException("Scene " + scene.getClass().getName() + " must be a public class or public static class, " +
                    "and have only one parameterless constructor to be properly recreated from instance state.");
        }

        mGroupSceneManager.add(viewId, scene, tag, animationOrAnimatorFactory);
    }

    @Override
    public final void disableSupportRestore() {
        this.mSupportRestore = false;
    }

    @Override
    public final boolean isSupportRestore() {
        return this.mSupportRestore;
    }


    public final <T extends Scene> T findSceneByTag(String tag) {
        ThreadUtility.checkUIThread();

        if (tag == null) {
            return null;
        }

        GroupRecord record = this.mGroupSceneManager.findByTag(tag);
        if (record != null) {
            return (T) record.scene;
        } else {
            return null;
        }
    }


    public final String findTagByScene(Scene scene) {
        ThreadUtility.checkUIThread();

        if (scene == null) {
            return null;
        }

        GroupRecord record = this.mGroupSceneManager.findByScene(scene);
        if (record != null) {
            return record.tag;
        } else {
            return null;
        }
    }

    public final void remove(Scene scene) {
        this.remove(scene, EMPTY_ANIMATION_FACTORY);
    }

    public final void remove(final Scene scene, Animator nextAnim) {
        this.remove(scene, buildAnimatorFactory(scene, nextAnim));
    }

    private static AnimationOrAnimatorFactory buildAnimatorFactory(final Scene scene, Animator nextAnim) {
        return new AnimationOrAnimatorFactory() {
            @Override
            public AnimationOrAnimator getAnimationOrAnimator() {
                return AnimationOrAnimator.loadAnimation(scene.requireActivity(), nextAnim);
            }
        };
    }

    private void remove(final Scene scene, final AnimationOrAnimatorFactory factory) {
        ThreadUtility.checkUIThread();
        this.mGroupSceneManager.remove(scene, factory);
    }

    public final void hide(Scene scene) {
        this.hide(scene, EMPTY_ANIMATION_FACTORY);
    }

    public final void hide(final Scene scene, Animator nextAnim) {
        this.hide(scene, buildAnimatorFactory(scene, nextAnim));
    }

    private void hide(Scene scene, AnimationOrAnimatorFactory factory) {
        ThreadUtility.checkUIThread();
        this.mGroupSceneManager.hide(scene, factory);
    }

    public final void show(Scene scene) {
        this.show(scene, EMPTY_ANIMATION_FACTORY);
    }

    public final void show(final Scene scene, Animator nextAnim) {
        this.show(scene, buildAnimatorFactory(scene, nextAnim));
    }

    private void show(Scene scene, AnimationOrAnimatorFactory factory) {
        ThreadUtility.checkUIThread();
        this.mGroupSceneManager.show(scene, factory);
    }

    public final boolean isAdded(Scene scene) {
        return this.mGroupSceneManager.findByScene(scene) != null;
    }

    public final boolean isShowing(Scene scene) {
        GroupRecord record = this.mGroupSceneManager.findByScene(scene);
        if (record == null) {
            return false;
        }
        return !record.isHidden;
    }

    /**
     * use {@link #isShowing(Scene)} instead
     * @param scene 场景
     * @return 是否显示
     */
    @Deprecated
    public final boolean isShow(Scene scene) {
        return isShowing(scene);
    }


    final ComponentContainer findContainerById(int viewId) {
        ComponentContainer viewGroup = (ComponentContainer) getView().findComponentById(viewId);
        if (viewGroup == null) {
            try {
                String viewIdName = getResources().getElement(viewId).getString();
                throw new IllegalArgumentException(" " + viewIdName + " view not found");
            } catch (NotExistException | WrongTypeException | IOException e) {
                throw new IllegalArgumentException(" " + viewId + " view not found");
            }
        } else {
            ComponentContainer tmp = viewGroup;
            while (tmp != null) {
                if (tmp == getView()) {
                    break;
                }
                Scene scene = (Scene) tmp.getTag();
                if (scene != null) {
                    throw new IllegalArgumentException(String.format("cant add Scene to child Scene %s view hierarchy ", scene.toString()));
                }
                tmp = (ComponentContainer) tmp.getComponentParent();
            }
        }
        return viewGroup;
    }

    @Override
    public final void dispatchAttachActivity(Ability activity) {
        super.dispatchAttachActivity(activity);
    }

    @Override
    public final void dispatchAttachScene(Scene parentScene) {
        super.dispatchAttachScene(parentScene);
        if (parentScene == null) {
            //ignore
        } else if (parentScene instanceof SceneParent) {
            SceneParent sceneParent = (SceneParent) parentScene;
            if (!sceneParent.isSupportRestore()) {
                disableSupportRestore();
            }
        } else {
            throw new SceneInternalException("unknown parent Scene type " + parentScene.getClass());
        }
    }

    @Override
    public final void dispatchCreate(Intent savedInstanceState) {
        super.dispatchCreate(savedInstanceState);
    }

    @Override
    public void onCreate(Intent savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            boolean supportRestore = savedInstanceState.getBooleanParam(KEY_GROUP_SCENE_SUPPORT_RESTORE_ARGUMENT, isSupportRestore());
            if (!supportRestore) {
                disableSupportRestore();
            }
            if (isSupportRestore()) {
                this.mGroupSceneManager.restoreFromBundle(requireActivity(), savedInstanceState);
            }
        }
    }

    @Override
    public final void dispatchCreateView(Intent savedInstanceState, ComponentContainer container) {
        super.dispatchCreateView(savedInstanceState, container);
        Component view = getView();
        if (!(view instanceof ComponentContainer)) {
            throw new IllegalArgumentException("GroupScene onCreateView view must be ViewGroup");
        }
        this.mGroupSceneManager.setView((ComponentContainer) getView());
        dispatchChildrenState(State.VIEW_CREATED);
        replacePlaceHolderViewToTargetScene();
    }

    private void replacePlaceHolderViewToTargetScene() {
        List<ScenePlaceHolderView> holderViewList = new ArrayList<>();
        extractScenePlaceHolder(holderViewList, (ComponentContainer) requireView());
        if (holderViewList.size() == 0) {
            return;
        }

        if (isSupportRestore()) {
            //We can't handle user remove Scene, then add same tag Scene to another ViewGroup
            throw new IllegalStateException("ScenePlaceHolderView can only be used when support restore is disabled");
        }

        HashMap<Integer, ComponentContainer> parentIdViewMap = new HashMap<Integer, ComponentContainer>();
        for (int i = 0, N = holderViewList.size(); i < N; i++) {
            ScenePlaceHolderView holderView = holderViewList.get(i);
            ComponentContainer parent = (ComponentContainer) holderView.getComponentParent();
            int parentId = parent.getId();
            if (parentId == Component.ID_DEFAULT) {
                throw new IllegalArgumentException("ScenePlaceHolderView parent id can't be View.NO_ID");
            }
            if (parentIdViewMap.get(parentId) == null) {
                parentIdViewMap.put(parentId, parent);
            } else if (parentIdViewMap.get(parentId) != parent) {
                throw new IllegalArgumentException("ScenePlaceHolderView' parent ViewGroup should have unique id," +
                        " the duplicate id is " + Utility.getIdName(requireSceneContext(), parentId));
            }
            ComponentContainer.LayoutConfig layoutParams = holderView.getLayoutConfig();
            String name = holderView.getSceneName();
            String tag = holderView.getSceneTag();
            IntentParams arguments = holderView.getArguments();

            Scene scene = null;
            SceneComponentFactory componentFactory = holderView.getSceneComponentFactory();
            if (componentFactory != null) {
                scene = componentFactory.instantiateScene(requireSceneContext().getClassloader(), name, arguments);
            }
            if (scene == null) {
                scene = SceneInstanceUtility.getInstanceFromClassName(requireSceneContext(), name, arguments);
            }
            int index = parent.getChildIndex(holderView);
            parent.removeComponent(holderView);
            if (holderView.getVisibility() == Component.VISIBLE) {
                add(parentId, scene, tag);
            } else if (holderView.getVisibility() == Component.HIDE) {
                beginTransaction();
                add(parentId, scene, tag);
                hide(scene);
                commitTransaction();
            } else {
                throw new IllegalStateException("ScenePlaceHolderView's visibility can't be View.INVISIBLE, use View.VISIBLE or View.GONE instead");
            }
            Component sceneView = scene.requireView();
            if (holderView.getId() != Component.ID_DEFAULT) {
                if (sceneView.getId() == Component.ID_DEFAULT) {
                    sceneView.setId(holderView.getId());
                } else if (holderView.getId() != sceneView.getId()) {
                    String holderViewIdName = Utility.getIdName(requireSceneContext(), holderView.getId());
                    String sceneViewIdName = Utility.getIdName(requireSceneContext(), sceneView.getId());
                    throw new IllegalStateException(String.format("ScenePlaceHolderView's id %s is different from Scene root view's id %s"
                            , holderViewIdName, sceneViewIdName));
                }
            }
            parent.removeComponent(sceneView);
            parent.addComponent(sceneView, index, layoutParams);
        }
    }

    private static void extractScenePlaceHolder(List<ScenePlaceHolderView> list, ComponentContainer viewGroup) {
        int count = viewGroup.getChildCount();
        if (count == 0) {
            return;
        }
        for (int i = 0; i < count; i++) {
            Component view = viewGroup.getComponentAt(i);
            if (view instanceof ScenePlaceHolderView) {
                list.add((ScenePlaceHolderView) view);
            } else if (view instanceof ComponentContainer) {
                extractScenePlaceHolder(list, (ComponentContainer) view);
            }
        }
    }

    public abstract ComponentContainer onCreateView(LayoutScatter inflater, ComponentContainer container,
                                                    Intent savedInstanceState);

    @Override
    public final void dispatchActivityCreated(Intent savedInstanceState) {
        super.dispatchActivityCreated(savedInstanceState);
        // Child scene's savedInstanceState is saved separately (in GroupRecord).
        dispatchChildrenState(State.ACTIVITY_CREATED);
        onPostActivityCreated();
    }

    /**
     * Method order:
     * <p>
     * Parent onActivityCreated()
     * Child onActivityCreated()
     * Child onPostActivityCreated()
     * Parent onPostActivityCreated()
     * <p>
     * At this moment, all children Scene have finished onActivityCreated()
     */
    @Experimental
    protected void onPostActivityCreated() {

    }

    @Override
    public final void dispatchSaveInstanceState(Intent outState) {
        super.dispatchSaveInstanceState(outState);
    }

    public void onSaveInstanceState(Intent outState) {
        super.onSaveInstanceState(outState);
        if (outState.getParams().hasParam(KEY_GROUP_SCENE_SUPPORT_RESTORE_ARGUMENT)) {
            throw new IllegalArgumentException("outState already contains key " + KEY_GROUP_SCENE_SUPPORT_RESTORE_ARGUMENT);
        } else {
            outState.setParam(KEY_GROUP_SCENE_SUPPORT_RESTORE_ARGUMENT, isSupportRestore());
            if (isSupportRestore()) {
                this.mGroupSceneManager.saveToBundle(outState);
            }
        }
    }

    @Override
    public final void dispatchStart() {
        super.dispatchStart();
        dispatchVisibleChildrenState(State.STARTED);
        onPostStart();
    }

    /**
     * Method order:
     * <p>
     * Parent onStart()
     * Child onStart()
     * Child onPostStart()
     * Parent onPostStart()
     * <p>
     * At this moment, all children Scene have finished onStart()
     */
    @Experimental
    protected void onPostStart() {

    }

    @Override
    public final void dispatchResume() {
        super.dispatchResume();
        dispatchVisibleChildrenState(State.RESUMED);
        onPostResume();
    }

    /**
     * Method order:
     * <p>
     * Parent onResume()
     * Child onResume()
     * Child onPostResume()
     * Parent onPostResume()
     * <p>
     * At this moment, all children Scene have finished onResume()
     */
    @Experimental
    protected void onPostResume() {

    }

    @Override
    public final void dispatchPause() {
        dispatchVisibleChildrenState(State.STARTED);
        super.dispatchPause();
    }

    @Override
    public final void dispatchStop() {
        dispatchVisibleChildrenState(State.ACTIVITY_CREATED);
        super.dispatchStop();
    }

    @Override
    public final void dispatchDestroyView() {
        dispatchChildrenState(State.NONE);
        super.dispatchDestroyView();
    }

    @Override
    public final void dispatchDestroy() {
        super.dispatchDestroy();
    }

    @Override
    public final void dispatchDetachScene() {
        super.dispatchDetachScene();
    }

    @Override
    public final void dispatchDetachActivity() {
        super.dispatchDetachActivity();
    }

    private void dispatchChildrenState(State state) {
        this.mGroupSceneManager.dispatchChildrenState(state);
    }

    private void dispatchVisibleChildrenState(State state) {
        this.mGroupSceneManager.dispatchVisibleChildrenState(state);
    }


    public final <T extends Scene> T createOrReuse(String tag, Creator<T> creator) {
        Scene scene = findSceneByTag(tag);
        if (scene == null) {
            scene = creator.call();
        }
        return (T) scene;
    }

    public final void registerChildSceneLifecycleCallbacks(ChildSceneLifecycleCallbacks cb, boolean recursive) {
        ThreadUtility.checkUIThread();
        this.mLifecycleCallbacks.add(NonNullPair.create(cb, recursive));
    }

    public final void unregisterChildSceneLifecycleCallbacks(ChildSceneLifecycleCallbacks cb) {
        ThreadUtility.checkUIThread();
        NonNullPair<ChildSceneLifecycleCallbacks, Boolean> target = null;
        for (int i = 0, N = this.mLifecycleCallbacks.size(); i < N; i++) {
            if (this.mLifecycleCallbacks.get(i).first == cb) {
                target = this.mLifecycleCallbacks.get(i);
                break;
            }
        }
        if (target != null) {
            this.mLifecycleCallbacks.remove(target);
        }
    }

    @Override
    public String getSceneDebugInfo(Scene scene) {
        if (scene.getParentScene() == null) {
            return null;
        }
        if (scene.getParentScene() != this) {
            throw new IllegalArgumentException("Scene parent is incorrect");
        }
        String tag = findTagByScene(scene);
        boolean isHidden = !isShow(scene);

        StringBuilder stringBuilder = new StringBuilder("tag: " + tag + " ");
        if (isHidden) {
            stringBuilder.append("hidden ");
        }

        return stringBuilder.toString();
    }

    @Override
    public final void dispatchOnSceneCreated(Scene scene, Intent savedInstanceState, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneCreated(scene, savedInstanceState);
                }
            }
        }

        super.dispatchOnSceneCreated(scene, savedInstanceState, directChild);
    }

    @Override
    public final void dispatchOnSceneViewCreated(Scene scene, Intent savedInstanceState, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneViewCreated(scene, savedInstanceState);
                }
            }
        }

        super.dispatchOnSceneViewCreated(scene, savedInstanceState, directChild);
    }

    @Override
    public final void dispatchOnSceneActivityCreated(Scene scene, Intent savedInstanceState, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneActivityCreated(scene, savedInstanceState);
                }
            }
        }

        super.dispatchOnSceneActivityCreated(scene, savedInstanceState, directChild);
    }

    @Override
    public final void dispatchOnSceneStarted(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneStarted(scene);
                }
            }
        }

        super.dispatchOnSceneStarted(scene, directChild);
    }

    @Override
    public final void dispatchOnSceneResumed(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneResumed(scene);
                }
            }
        }

        super.dispatchOnSceneResumed(scene, directChild);
    }

    @Override
    public final void dispatchOnSceneStopped(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneStopped(scene);
                }
            }
        }

        super.dispatchOnSceneStopped(scene, directChild);
    }

    @Override
    public final void dispatchOnScenePaused(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onScenePaused(scene);
                }
            }
        }

        super.dispatchOnScenePaused(scene, directChild);
    }

    @Override
    public final void dispatchOnSceneSaveInstanceState(Scene scene, Intent outState, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneSaveInstanceState(scene, outState);
                }
            }
        }

        super.dispatchOnSceneSaveInstanceState(scene, outState, directChild);
    }

    @Override
    public final void dispatchOnSceneViewDestroyed(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneViewDestroyed(scene);
                }
            }
        }

        super.dispatchOnSceneViewDestroyed(scene, directChild);
    }

    @Override
    public final void dispatchOnSceneDestroyed(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneDestroyed(scene);
                }
            }
        }

        super.dispatchOnSceneDestroyed(scene, directChild);
    }
}
